package com.fxl.majiang.websocket;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fxl.majiang.config.UserConfig;
import com.fxl.majiang.enums.*;
import com.fxl.majiang.exception.CustomException;
import com.fxl.majiang.message.MessageHandler;
import com.fxl.majiang.message.input.ChiMessageData;
import com.fxl.majiang.message.input.OperationMessage;
import com.fxl.majiang.message.output.*;
import com.fxl.majiang.room.*;
import com.fxl.majiang.service.RoomService;
import com.fxl.majiang.service.UserService;
import com.fxl.majiang.user.User;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author fengxinglei
 * @date 2022/8/1 16:10
 * @description 服务
 */
@Slf4j
@Component
@ServerEndpoint(value = "/websocket/{roomId}/{userId}")
public class WebSocketServer {

    // 在线数量
    private static AtomicInteger onlineCount = new AtomicInteger(0);

    private Session session;
    private String userId;

    private static UserConfig userConfig;

    private static UserService userService;

    private static RoomService roomService;

    
    public Session getSession() {
        return session;
    }

    public String getUserId() {
        return userId;
    }

    @Autowired
    public void setUserConfig(UserConfig userConfig) {
        WebSocketServer.userConfig = userConfig;
    }

    @Autowired
    public void setUserService(UserService userService) {
        WebSocketServer.userService = userService;
    }

    @Autowired
    public void setRoomService(RoomService roomService) {
        WebSocketServer.roomService = roomService;
    }

    @OnOpen
    public void onOpen(@PathParam("roomId") String roomId, @PathParam("userId") String userId, Session session) throws Exception {
        User user = getUser(userId);
        if (user == null) {
            throw new CustomException("用户不存在");
        }
        this.userId = userId;
        this.session = session;
        // 设置永不超时
        this.session.setMaxIdleTimeout(0);
        addOnlineCount();
        ClientManager.add(userId,this);
        log.warn("已连接:" + userId);
        if(!userService.check(userId)) {
            throw new CustomException("用户不存在");
        }
        // 先查询用户是否已经房间
        Room userRoom = RoomManager.getUserRoom(userId);
        if (userRoom == null) {
            throw new CustomException("未加入该房间");
        }
        if (!userRoom.getId().equals(roomId)) {
            throw new CustomException("你加入的房间id不正确");
        }
        Player player = userRoom.getPlayer(userId);
        if (player == null) {
            throw new CustomException("分配座位失败，请重试");
        }
        // 如果房间是准备中，则认为用户刚加入，还未开始游戏
        if (userRoom.getStatus() == RoomStatusEnum.PREPARING.getCode()) {
            join(player, userRoom);
        }
        // 如果房间正在游戏中，则给用户推送手牌
        else {
            reconnect(player, userRoom);
        }
    }

    // 玩家加入房间
    private void join(Player player, Room userRoom) {
        SeatEnum seat = player.getSeat();
        OutputJoinMsgData msgData = new OutputJoinMsgData();
        List<Player> allPlayers = userRoom.getAllPlayers();
        // 上家位次
        SeatEnum previousSeat = SeatEnum.getPreviousSeat(seat);
        // 下家位次
        SeatEnum nextSeat = SeatEnum.getNextSeat(seat);
        List<String> otherIds = new ArrayList<>();
        allPlayers.stream().forEach(p -> {
            String uid = p.getUserId();
            String name = userConfig.getByUserId(uid).getName();
            SeatEnum seatEnum = p.getSeat();
            if (p.getUserId().equals(userId)) {
                msgData.setSelfInfo(name, p);
            } else if (previousSeat == seatEnum) {
                otherIds.add(uid);
                msgData.setPreviousInfo(name, p);
            } else if (nextSeat == seatEnum) {
                otherIds.add(uid);
                msgData.setNextInfo(name, p);
            } else {
                otherIds.add(uid);
                msgData.setOppositeInfo(name, p);
            }
        });
        OutputMessage outputSelfMessage = new OutputMessage(OutputMsgTypeEnum.WELCOME.getCode(), msgData);
        MessageHandler.sendToUser(userId, outputSelfMessage);
        OutputMessage outputOtherMessage = new OutputMessage(OutputMsgTypeEnum.OTHER_JOIN.getCode(), new OutputOtherJoinMsgData(userId, userConfig.getByUserId(userId).getName(), seat.getCode()));
        MessageHandler.sendToUsers(otherIds, outputOtherMessage);
    }

    /**
     * 用户重连
     * @param selfPlayer
     * @param userRoom
     */
    public void reconnect(Player selfPlayer, Room userRoom) {
        SeatEnum selfSeat = selfPlayer.getSeat();
        OutputReconnectMsgData msgData = new OutputReconnectMsgData();
        Dealers dealers = userRoom.getDealers();
        msgData.setVersatileTiles(dealers.getVersatileTiles());
        msgData.setLeftTilesCount(dealers.getRemainingTiles().size());
        TilesEnum lastTile = userRoom.getLastTile();
        // 如果最近一张牌还有，则判断能否吃、碰、杠、点炮等操作
        if (lastTile != null) {
            msgData.setLastTile(lastTile);
            if(userRoom.isCurPlayer(selfPlayer.getUserId())) {
                // 是否可以吃
                List<List<TilesEnum>> chiItGroups = RuleManager.getCanChiItGroups(selfPlayer, lastTile, dealers.getVersatileTiles());
                msgData.setCanChi(!CollectionUtils.isEmpty(chiItGroups));
                msgData.setChiItGroups(chiItGroups);
            }
            // 是否可以点炮
            msgData.setCanDianPao(RuleManager.canDianPaoIt(selfPlayer, lastTile));
            // 是否可以明杠
            msgData.setCanGangIt(RuleManager.canMingGangIt(selfPlayer, lastTile));
            // 是否可以碰
            msgData.setCanPeng(RuleManager.canPengIt(selfPlayer, lastTile));
        }
        // 如果是自己正在操作，则加载自己可以做的操作
        if(userRoom.isCurPlayer(selfPlayer.getUserId())) {
            List<TilesEnum> ownedHuaTiles = selfPlayer.getOwnedHuaTiles();
            // 是否可以起始补花
            if (userRoom.isSupplementWhenStart()) {
               boolean canBuHuaWhenStart = false;
               if (!CollectionUtils.isEmpty(ownedHuaTiles)) {
                   for (TilesEnum huaTile: ownedHuaTiles) {
                       if (!dealers.isVersatile(huaTile)) {
                           canBuHuaWhenStart = true;
                           break;
                       }
                   }
               }
               msgData.setCanBuHuaWhenStart(canBuHuaWhenStart);
            } else {// 是否可以普通补花
                if (!CollectionUtils.isEmpty(ownedHuaTiles)) {
                    msgData.setCanBuHua(true);
                }
            }
            // 是否可以花暗杠
            List<List<TilesEnum>> huaAnGangGroups = selfPlayer.getHuaAnGangGroups();
            if (!CollectionUtils.isEmpty(huaAnGangGroups)) {
                msgData.setCanHuaAnGang(true);
                msgData.setHuaAnGangGroups(huaAnGangGroups);
            }
            // 是否可以自摸
            msgData.setCanZiMo(RuleManager.canZiMo(selfPlayer, dealers.getVersatileTiles()));
            List<TilesEnum> anGangTiles = selfPlayer.getAnGangTiles();
            // 是否可以暗杠
            if (!CollectionUtils.isEmpty(anGangTiles)) {
                msgData.setAnGangTiles(anGangTiles);
                msgData.setCanAnGang(true);
            }
            // 摸排后是否可以明杠
            List<TilesEnum> mingGangTilesAfterGrabbing = selfPlayer.getMingGangTilesAfterGrabbing();
            if (!CollectionUtils.isEmpty(mingGangTilesAfterGrabbing)) {
                msgData.setMingGangTilesAfterGrabbing(mingGangTilesAfterGrabbing);
                msgData.setCanMingGangAfterGrabbing(true);
            }
        }

        // 判断是否当前用户正在操作
        msgData.setPlayingUserId(userRoom.getPlayingUserId());
        List<Player> allPlayers = userRoom.getAllPlayers();
        // 上家位次
        SeatEnum previousSeat = SeatEnum.getPreviousSeat(selfSeat);
        // 下家位次
        SeatEnum nextSeat = SeatEnum.getNextSeat(selfSeat);
        List<String> otherIds = new ArrayList<>();
        allPlayers.stream().forEach(p -> {
            String uid = p.getUserId();
            String name = userConfig.getByUserId(uid).getName();
            SeatEnum seatEnum = p.getSeat();
            if (p.getUserId().equals(userId)) {
                msgData.setSelfInfo(name, p);
            } else if (previousSeat == seatEnum) {
                otherIds.add(uid);
                msgData.setPreviousInfo(name, p);
            } else if (nextSeat == seatEnum) {
                otherIds.add(uid);
                msgData.setNextInfo(name, p);
            } else {
                otherIds.add(uid);
                msgData.setOppositeInfo(name, p);
            }
        });
        OutputMessage outputSelfMessage = new OutputMessage(OutputMsgTypeEnum.RECONNECT.getCode(), msgData);
        MessageHandler.sendToUser(userId, outputSelfMessage);
    }

    private User getUser(String userId){
        for (User user : userConfig.getUsers()) {
            if (user.getId().equals(userId)) {
                return user;
            }
        }
        return null;
    }

    @OnClose
    public void onClose() throws IOException {
        log.warn("用户断开连接:" + userId);
        ClientManager.remove(userId);
//        PlayerHandler.leave(userId);
        subOnlineCount();
    }

    //收到客户端消息后调用的方法
    @OnMessage
    public void onMessage(String message) {
        try {
            System.err.println(">>>>收到客户端消息<<<< message=" + message);
            OperationMessage operationMessage = JSON.parseObject(message, OperationMessage.class);
            int operation = operationMessage.getOperation();
            String data = operationMessage.getData();
            // 准备
            if (operation == OperationEnum.READY.getCode()) {
                PlayerHandler.ready(userId);
            }
            // 未准备
            else if (operation == OperationEnum.UN_READY.getCode()) {
                PlayerHandler.unReady(userId);
            }
            // 起手补花
            else if (operation == OperationEnum.SUPPLEMENT_WHEN_START.getCode()) {
                PlayerHandler.supplementWhenStart(userId, JSON.parseArray(data, TilesEnum.class));
            }
            // 打牌
            else if (operation == OperationEnum.PLAY_THE_CARD.getCode()) {
                TilesEnum tile = TilesEnum.valueOf(data);
                PlayerHandler.playTheCard(userId, tile);
            }
            // 放弃操作
            else if (operation == OperationEnum.GIVE_UP_OPERATION.getCode()) {
                PlayerHandler.giveUpOperation(userId);
            }
            // 摸牌
            else if (operation == OperationEnum.GRABBING.getCode()) {
                PlayerHandler.grabbing(userId);
            }
            // 吃牌
            else if (operation == OperationEnum.CHI.getCode()) {
                if (!StringUtils.hasText(data)) {
                    throw new CustomException("请传递数据");
                }
                ChiMessageData chiMessageData = JSON.parseObject(data, ChiMessageData.class);
                String[] selectedGroupArr = chiMessageData.getSelectedGroup().split(",");
                List<TilesEnum> selectedGroupTiles = Arrays.stream(selectedGroupArr)
                        .map(TilesEnum::valueOf).collect(Collectors.toList());
                PlayerHandler.chi(userId, TilesEnum.valueOf(chiMessageData.getIt()), selectedGroupTiles);
            }
            // 碰牌
            else if (operation == OperationEnum.PENG.getCode()) {
                if (!StringUtils.hasText(data)) {
                    throw new CustomException("请传递数据");
                }
                PlayerHandler.peng(userId, TilesEnum.valueOf(data));
            }
            // 补花
            else if (operation == OperationEnum.SUPPLEMENT.getCode()) {
                if (!StringUtils.hasText(data)) {
                    throw new CustomException("请传递数据");
                }
                String[] tileStrArr = operationMessage.getData().split(",");
                List<TilesEnum> tiles = Arrays.stream(tileStrArr)
                        .map(TilesEnum::valueOf).collect(Collectors.toList());
                PlayerHandler.supplement(userId, tiles, false);
            }
            // 明杠打出的牌
            else if (operation == OperationEnum.MING_GANG_IT.getCode()) {
                TilesEnum tile = TilesEnum.valueOf(operationMessage.getData());
                PlayerHandler.mingGangIt(userId, tile);
            }
            // 抓牌之后明杠
            else if (operation == OperationEnum.MING_GANG_AFTER_GRABBING.getCode()) {
                TilesEnum tile = TilesEnum.valueOf(operationMessage.getData());
                PlayerHandler.mingGangItAfterGrabbing(userId, tile);
            }
            // 暗杠
            else if (operation == OperationEnum.AN_GANG.getCode()) {
                TilesEnum tile = TilesEnum.valueOf(operationMessage.getData());
                PlayerHandler.anGang(userId, tile);
            }
            // 花暗杠
//            else if (operation == OperationEnum.HUA_AN_GANG.getCode()) {
//                CardTypeEnum type = CardTypeEnum.valueOf(operationMessage.getData());
//                PlayerHandler.huaAnGang(userId, type);
//            }
            // 点炮
            else if (operation == OperationEnum.DIAN_PAO.getCode()) {
                TilesEnum tile = TilesEnum.valueOf(operationMessage.getData());
                PlayerHandler.dianPao(userId, tile);
            }
            // 自摸胡
            else if (operation == OperationEnum.ZI_MO.getCode()) {
                PlayerHandler.ziMo(userId);
            }
            // 用户离开房间
            else if (operation == OperationEnum.LEAVE_ROOM.getCode()) {
                PlayerHandler.leave(userId);
            }
            else {
                throw new CustomException("无效指令");
            }
        } catch (CustomException e) {
            OutputMessage outputMessage = new OutputMessage(OutputMsgTypeEnum.ERROR.getCode(), e.getMessage());
            sendToSession(session, outputMessage);
            e.printStackTrace();
        } catch (Exception e) {
            OutputMessage outputMessage = new OutputMessage(OutputMsgTypeEnum.ERROR.getCode(), "系统错误");
            sendToSession(session, outputMessage);
            e.printStackTrace();
        }

    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误", error);
        OutputMessage outputMessage = new OutputMessage(OutputMsgTypeEnum.ERROR.getCode(), error.getMessage());
        sendToSession(session, outputMessage);
    }

    @SneakyThrows
    public static void sendToSession(Session session, OutputMessage outputMessage) {
        session.getBasicRemote().sendText(JSON.toJSONString(outputMessage));
    }

    @SneakyThrows
    public static void sendMessageTo(JSONObject json) {
        log.warn("sendMsg:" + json);
        for (WebSocketServer item : ClientManager.getClients().values()) {
            item.getSession().getBasicRemote().sendText(json.toJSONString());
        }
    }

    /**
     * 发送给所有客户端
     * @param outputMessage
     */
    @SneakyThrows
    public static void sendMsgToAllClient(OutputMessage outputMessage) {
        for (WebSocketServer item : ClientManager.getClients().values()) {
            item.getSession().getBasicRemote().sendText(JSON.toJSONString(outputMessage));
        }
    }

    public static void clearMessageFromOpen(String message) {
        try {
            JSONObject json = JSONObject.parseObject(message);
            if (json.get("uuid") != null) {
            }
        } catch (Exception e) {
        }
    }


    public static synchronized int getOnlineCount() {
        return onlineCount.get();
    }

    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount.incrementAndGet();
    }

    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount.decrementAndGet();
    }
}
